#include "kn_wrapper.h"

#include <cstdint>
#include <future>
#include <napi/native_api.h>
#include <string>
#include <unistd.h>

#include "HiTraceSystraceSection.h"
#include "hilog/log.h"

napi_value kn_create_array(napi_env env) {
    napi_value array;
    napi_create_array(env, &array);
    return array;
}

uint32_t kn_get_array_length(napi_env env, napi_value value) {
    uint32_t length;
    napi_get_array_length(env, value, &length);
    return length;
}

napi_value kn_get_element(napi_env env, napi_value value, int index) {
    napi_value result;
    napi_get_element(env, value, index, &result);
    return result;
}

void kn_set_element(napi_env env, napi_value array, int index, napi_value value) {
    napi_set_element(env, array, index, value);
}

bool kn_is_array(napi_env env, napi_value value) {
    bool result = false;
    napi_is_array(env, value, &result);
    return result;
}

bool kn_is_arraybuffer(napi_env env, napi_value value) {
    bool isArrayBuffer;
    napi_is_arraybuffer(env, value, &isArrayBuffer);
    return isArrayBuffer;
}

bool kn_is_typedarray(napi_env env, napi_value value) {
    bool isTypedArray;
    napi_is_typedarray(env, value, &isTypedArray);
    return isTypedArray;
}

size_t kn_get_arraybuffer_length(napi_env env, napi_value value) {
    size_t length;
    void *data;
    napi_get_arraybuffer_info(env, value, &data, &length);
    return length;
}

uint8_t *kn_get_arraybuffer_value(napi_env env, napi_value value) {
    void *data;
    size_t length;
    napi_get_arraybuffer_info(env, value, &data, &length);
    return static_cast<uint8_t *>(data);
}

napi_typedarray_type kn_get_typedarray_type(napi_env env, napi_value value) {
    napi_typedarray_type type;
    napi_value buffer;
    size_t length;
    size_t offset;
    void *data;
    napi_get_typedarray_info(env, value, &type, &length, &data, &buffer, &offset);
    return type;
}

uint8_t *kn_get_typedarray_value(napi_env env, napi_value value) {
    napi_typedarray_type type;
    napi_value buffer;
    size_t length;
    size_t offset;
    void *data;
    napi_get_typedarray_info(env, value, &type, &length, &data, &buffer, &offset);
    return static_cast<uint8_t *>(data);
}

size_t kn_get_typedarray_length(napi_env env, napi_value value) {
    napi_typedarray_type type;
    napi_value buffer;
    size_t length;
    size_t offset;
    void *data;
    napi_get_typedarray_info(env, value, &type, &length, &data, &buffer, &offset);
    return length;
}

napi_value kn_create_arraybuffer(napi_env env, uint8_t *ptr, size_t length) {
    napi_value arrayBuffer;
    uint8_t *outputBuff = nullptr;
    napi_create_arraybuffer(env, length, reinterpret_cast<void **>(&outputBuff), &arrayBuffer);
    uint8_t *inputBytes = ptr;
    for (size_t i = 0; i < length; i++) {
        outputBuff[i] = inputBytes[i];
    }
    return arrayBuffer;
}

size_t kn_get_typedarray_item_size(napi_typedarray_type typed) {
    size_t size = 1;
    switch (typed) {
    case napi_int8_array:
    case napi_uint8_array:
    case napi_uint8_clamped_array:
        size = 1;
        break;
    case napi_int16_array:
    case napi_uint16_array:
        size = sizeof(uint16_t);
        break;
    case napi_int32_array:
    case napi_uint32_array:
        size = sizeof(uint32_t);
        break;
    case napi_float32_array:
        size = sizeof(float);
        break;
    case napi_float64_array:
        size = sizeof(double);
        break;
    case napi_bigint64_array:
    case napi_biguint64_array:
        size = sizeof(uint64_t);
        break;
    default:
        size = 1;
        break;
    }
    return size;
}

napi_value kn_create_typedarray(napi_env env, uint8_t *data, size_t count, napi_typedarray_type typed) {
    size_t length = count * kn_get_typedarray_item_size(typed);
    napi_value arrayBuffer = kn_create_arraybuffer(env, data, length);
    napi_value typedArray;
    napi_create_typedarray(env, typed, count, arrayBuffer, 0, &typedArray);
    return typedArray;
}

int kn_get_cb_info_argc(napi_env env, napi_callback_info info) {
    size_t argc;
    napi_get_cb_info(env, info, &argc, NULL, NULL, NULL);
    return argc;
}

void *kn_get_cb_info_data(napi_env env, napi_callback_info info) {
    void *data = NULL;
    napi_get_cb_info(env, info, 0, NULL, NULL, &data);
    return data;
}

napi_value *kn_get_cb_info_args(napi_env env, napi_callback_info info, int size) {
    size_t argc = (size_t)size;
    napi_value *args = (napi_value *)malloc((size) * sizeof(napi_value));
    napi_get_cb_info(env, info, &argc, args, NULL, NULL);
    return args;
}

napi_value kn_get_cb_info_jsThis(napi_env env, napi_callback_info info) {
    napi_value jsThis;
    napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr);
    return jsThis;
}

napi_value kn_new_instance(napi_env env, napi_value constructor) {
    napi_value result;
    napi_new_instance(env, constructor, 0, NULL, &result);
    return result;
}

typedef void *(*callJSFunction)(void *data);

#ifdef DEBUG
// 同步调用最大超时时间
constexpr int kThreadSafeFunctionMaxTimeoutSeconds = 10;
#endif

struct CallbackData {
    void *data;
    void *callback;
    std::promise<void *> result;
    bool sync;
};

napi_value kn_call_function(napi_env env, napi_value recv, napi_value func, int size, const napi_value *argv,
                        napi_value *exceptionObj) {
    napi_value result;
    auto status = napi_call_function(env, recv, func, size, argv, &result);
    if (exceptionObj != nullptr && status == napi_pending_exception) {
        napi_get_and_clear_last_exception(env, exceptionObj);
    }
    return result;
}

napi_value kn_create_function(napi_env env, const char *name, napi_callback callback, void *release) {
    napi_value result;
    napi_create_function(env, name, NAPI_AUTO_LENGTH, callback, release, &result);
    return result;
}

napi_threadsafe_function kn_create_threadsafe_function_with_callback(napi_env env, const char *workName, void *callback) {
    napi_value workNameNapiValue = 0;
    napi_create_string_utf8(env, workName, NAPI_AUTO_LENGTH, &workNameNapiValue);
    napi_threadsafe_function tsfn;
    napi_create_threadsafe_function(env, 0, NULL, workNameNapiValue, 0, 1, NULL, NULL, NULL,
                                    (napi_threadsafe_function_call_js)callback, &tsfn);
    return tsfn;
}

void kn_call_threadsafe_function_with_data(napi_threadsafe_function tsfn, void *data) {
    napi_acquire_threadsafe_function(tsfn);
    napi_call_threadsafe_function(tsfn, data, napi_tsfn_nonblocking);
}

static void kn_call_js(napi_env env, napi_value noUsed, void *context, void *data) {
    auto *callbackData = reinterpret_cast<CallbackData *>(data);
    napi_handle_scope scope;
    napi_open_handle_scope(env, &scope);
    auto callback = (callJSFunction)callbackData->callback;
    auto result = callback(callbackData->data);
    if (callbackData->sync) {
        callbackData->result.set_value(result);
    }
    napi_close_handle_scope(env, scope);
}

napi_threadsafe_function kn_create_threadsafe_function_sync(napi_env env, const char *workName) {
    napi_value workNameNapiValue = nullptr;
    napi_create_string_utf8(env, workName, NAPI_AUTO_LENGTH, &workNameNapiValue);
    napi_threadsafe_function tsfn;
    napi_create_threadsafe_function(env, 0, NULL, workNameNapiValue, 0, 1, NULL, NULL, NULL,
                                    (napi_threadsafe_function_call_js)kn_call_js, &tsfn);
    return tsfn;
}

void *kn_call_threadsafe_function(napi_threadsafe_function tsfn, void *callback, void *data, bool sync, int tsfnOriginTid) {
    napi_acquire_threadsafe_function(tsfn);
    auto *callbackData = new CallbackData{.data = data, .callback = callback, .result = {}, .sync = sync};
    auto mainTid = getpid();
    if (tsfnOriginTid == mainTid) {
        napi_call_threadsafe_function_with_priority(tsfn, callbackData, napi_priority_high, true);
    } else {
        napi_call_threadsafe_function(tsfn, reinterpret_cast<void *>(callbackData), napi_tsfn_blocking);
    }
    if (sync) {
        auto future = callbackData->result.get_future();
#ifdef DEBUG
        if (tsfnOriginTid == mainTid) {
            return future.get();
        } else {
            auto state = future.wait_for(std::chrono::seconds(kThreadSafeFunctionMaxTimeoutSeconds));
            if (state == std::future_status::ready) {
                return future.get();
            } else {
                OH_LOG_Print(LogType::LOG_APP, LOG_INFO, 1u, "knoi", "thread safe function timeout.");
                abort();
            }
        }
#else
        return future.get();
#endif
    } else {
        return nullptr;
    }
}

napi_status kn_load_module_with_info(napi_env env, const char *path, const char *module_info, napi_value *result) {
    return napi_load_module_with_info(env, path, module_info, result);
}

napi_status kn_load_module(napi_env env, const char *path, napi_value *result) {
    return napi_load_module(env, path, result);
}

napi_value kn_create_object(napi_env env) {
    napi_value obj;
    napi_create_object(env, &obj);
    return obj;
}

napi_value kn_get_global(napi_env env) {
    napi_value global;
    napi_get_global(env, &global);
    return global;
}

napi_value kn_get_undefined(napi_env env) {
    napi_value undefined;
    napi_get_undefined(env, &undefined);
    return undefined;
}

napi_value kn_get_null(napi_env env) {
    napi_value result;
    napi_get_null(env, &result);
    return result;
}

void kn_wrap(napi_env env, napi_value js_object, void *finalize_data, void *finalize_cb) {
    napi_wrap(env, js_object, finalize_data, (napi_finalize)finalize_cb, nullptr, nullptr);
}

void *kn_unwrap(napi_env env, napi_value js_object) {
    void *nativeObj;
    napi_unwrap(env, js_object, &nativeObj);
    return nativeObj;
}

bool kn_equals(napi_env env, napi_value a, napi_value b) {
    bool result;
    napi_strict_equals(env, a, b, &result);
    return result;
}

bool kn_instanceof(napi_env env, napi_value constructor, napi_value object) {
    bool result;
    napi_instanceof(env, object, constructor, &result);
    return result;
}

napi_value kn_get_property_names(napi_env env, napi_value value) {
    napi_value names;
    napi_get_property_names(env, value, &names);
    return names;
}

napi_value kn_get_property(napi_env env, napi_value obj, napi_value key) {
    napi_value result;
    napi_get_property(env, obj, key, &result);
    return result;
}

napi_value kn_get_named_property(napi_env env, napi_value obj, const char *key) {
    napi_value result;
    napi_get_named_property(env, obj, key, &result);
    return result;
}

void kn_set_named_property(napi_env env, napi_value obj, const char *key, napi_value value) {
    napi_set_named_property(env, obj, key, value);
}

/***
 * 创建引用，防止被回收，不再使用时请调用 deleteReference
 */
napi_ref kn_create_reference(napi_env env, napi_value value) {
    napi_ref ref;
    napi_create_reference(env, value, 1, &ref);
    return ref;
}

void kn_delete_reference(napi_env env, napi_ref ref) { napi_delete_reference(env, ref); }

napi_value kn_get_reference_value(napi_env env, napi_ref ref) {
    napi_value value;
    napi_get_reference_value(env, ref, &value);
    return value;
}

char *kn_to_string(napi_env env, napi_value value) {
    size_t length = 0;
    napi_get_value_string_utf8(env, value, NULL, 0, &length);
    char *c_str = (char *)malloc((length + 1) * sizeof(char));
    napi_get_value_string_utf8(env, value, c_str, length + 1, &length);
    return c_str;
}

napi_value kn_string_to_napi(napi_env env, const char *value) {
    napi_value result = NULL;
    napi_create_string_utf8(env, value, strlen(value), &result);
    return result;
}

int kn_to_int(napi_env env, napi_value value) {
    int32_t result;
    napi_get_value_int32(env, value, &result);
    return result;
}

napi_value kn_int_to_napi(napi_env env, int value) {
    napi_value result = NULL;
    int32_t num = (int32_t)(value);
    napi_create_int32(env, num, &result);
    return result;
}

long kn_to_long(napi_env env, napi_value value) {
    int64_t result;
    napi_get_value_int64(env, value, &result);
    return result;
}

napi_value kn_long_to_napi(napi_env env, long value) {
    napi_value result = NULL;
    int64_t num = (int64_t)(value);
    napi_create_int64(env, num, &result);
    return result;
}

bool kn_to_boolean(napi_env env, napi_value value) {
    bool result;
    napi_get_value_bool(env, value, &result);
    return result;
}

napi_value kn_boolean_to_napi(napi_env env, bool value) {
    napi_value result = NULL;
    napi_get_boolean(env, value, &result);
    return result;
}

double kn_to_double(napi_env env, napi_value value) {
    double result;
    napi_get_value_double(env, value, &result);
    return result;
}

napi_value kn_double_to_napi(napi_env env, double value) {
    napi_value result = NULL;
    napi_create_double(env, value, &result);
    return result;
}

napi_valuetype kn_typeof(napi_env env, napi_value value) {
    napi_valuetype type;
    napi_typeof(env, value, &type);
    return type;
}
